package com.example.recorder;

import android.media.AudioFormat;
import android.media.AudioRecord;
import android.media.MediaRecorder;
import android.os.Build;
import android.support.annotation.RequiresApi;

import java.io.File;
import java.io.FileOutputStream;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;

/**
 * @author hdz
 * @date 2019/06/24
 */
public class AudioRecorder {
    private static final String TAG = "AudioRecorder";
    private final String mSdcardPath;
    private String mPcmPath;
    private int iSampleRate;
    private int iChannel;
    private int iBit;
    private AudioRecord mAudioRecord;
    private WorkThread mWorkThread;
    private boolean mIsSaveRecord = true;
    private boolean bRecording = false;
    private int mAudioSource = MediaRecorder.AudioSource.MIC;
    private Callback mCallback;
    private int channelCount = 0;

    public AudioRecorder(String sdcardPath) {
        mSdcardPath = sdcardPath;
    }

    private void save(byte[] b, int len, String filePath) {
        try {
            File file = new File(filePath);
            //true，表示追加到文本
            FileOutputStream fos = new FileOutputStream(file, true);
            fos.write(b, 0, len);
            fos.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public void initRecorder(int iSampleRate, int iChannel, int iBit) {
        this.iSampleRate = iSampleRate;
        this.channelCount = iChannel;
        if (iChannel == 1) {
            this.iChannel = AudioFormat.CHANNEL_IN_MONO;
        } else if (iChannel == 2) {
            this.iChannel = AudioFormat.CHANNEL_IN_STEREO;
        }
        this.iBit = (iBit == 16) ? AudioFormat.ENCODING_PCM_16BIT : AudioFormat.ENCODING_PCM_8BIT;
        this.mPcmPath = mSdcardPath + "/" + iSampleRate + "_" + iChannel + "_" + iBit + ".pcm";
    }

    public int getAudioSessionId() {
        if (mAudioRecord != null) {
            return mAudioRecord.getAudioSessionId();
        }
        return -1;
    }

    public void start(boolean isSave) {
        mIsSaveRecord = isSave;
        start();
    }

    public void start() {
        mWorkThread = new WorkThread();
        mWorkThread.start();
    }

    public void stop() {
        bRecording = false;
        if (mWorkThread != null) {
            try {
                mWorkThread.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            mWorkThread = null;
        }

        if (mAudioRecord != null) {
            if (mAudioRecord.getRecordingState() == AudioRecord.RECORDSTATE_RECORDING) {
                mAudioRecord.stop();
            }
            if (mAudioRecord.getState() == AudioRecord.STATE_INITIALIZED) {
                mAudioRecord.release();
            }
            mAudioRecord = null;
        }
    }

    private String state2Str(int state) {
        switch (state) {
            case AudioRecord.STATE_INITIALIZED:
                return "AudioRecord.STATE_INITIALIZED";
            case AudioRecord.STATE_UNINITIALIZED:
                return "AudioRecord.STATE_UNINITIALIZED";
            default:
                return "Unknown State: " + state;
        }
    }

    private String audioState2Str(int state) {
        switch (state) {
            case AudioRecord.RECORDSTATE_RECORDING:
                return "AudioRecord.RECORDSTATE_RECORDING";
            case AudioRecord.RECORDSTATE_STOPPED:
                return "AudioRecord.RECORDSTATE_STOPPED";
            default:
                return "Unknown State: " + state;
        }
    }

    public void setAudioSource(int audioSource) {
        this.mAudioSource = audioSource;
    }

    public void setCallback(Callback callback) {
        this.mCallback = callback;
    }

    public int getChannelCount() {
        if (mAudioRecord == null) {
            return 1;
        }
        return mAudioRecord.getChannelCount();
    }


    public interface Callback {
        void onAudioRecorderError(int err);

        void onAudioDataChanged(byte[] data, int len);
    }

    private class WorkThread extends Thread {

        @RequiresApi(api = Build.VERSION_CODES.M)
        private AudioFormat createAudioFormat() {
            AudioFormat format;
            if (channelCount > 2) {
                int channelIndexMask = 0;
                switch (channelCount) {
                    case 4:
                        channelIndexMask = 0xf;
                        break;
                    case 8:
                        channelIndexMask = 0xff;
                        break;
                    default:
                        break;
                }
                format = new AudioFormat.Builder()
                        .setEncoding(iBit)
                        .setSampleRate(iSampleRate)
                        .setChannelIndexMask(channelIndexMask)
                        .build();
            } else {
                format = new AudioFormat.Builder()
                        .setEncoding(iBit)
                        .setSampleRate(iSampleRate)
                        .setChannelMask(iChannel)
                        .build();
            }
            return format;
        }

        @Override
        public void run() {
            super.run();

            int bufSize;
            if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
                AudioFormat format = createAudioFormat();

                Logger.d(TAG, "run: iChannel=" + iChannel + ",channelCount=" + channelCount);

                if (channelCount > 2) {
                    bufSize = AudioRecord.getMinBufferSize(iSampleRate, AudioFormat.CHANNEL_IN_MONO, iBit) * channelCount;
                } else {
                    bufSize = AudioRecord.getMinBufferSize(iSampleRate, iChannel, iBit);
                }
                // 获得构建对象的最小缓冲区大小
                // 根据音频数据的特性来确定所要分配的缓冲区的最小size
                Logger.e(TAG, " " + bufSize + " " + iSampleRate + " " + format.getChannelCount() + " " + iBit);

                mAudioRecord = new AudioRecord.Builder()
                        .setAudioFormat(format)
                        .setAudioSource(MediaRecorder.AudioSource.MIC)
                        .setBufferSizeInBytes(bufSize)
                        .build();
            } else {
                bufSize = AudioRecord.getMinBufferSize(iSampleRate, iChannel, iBit);
                mAudioRecord = new AudioRecord(mAudioSource, iSampleRate, iChannel, iBit, bufSize);
            }

            int state = mAudioRecord.getState();
            if (state != AudioRecord.STATE_INITIALIZED) {
                Logger.e(TAG, "init state = " + state);
                if (mCallback != null) {
                    mCallback.onAudioRecorderError(1);
                }
            }

            mAudioRecord.startRecording();

            state = mAudioRecord.getRecordingState();
            if (state != AudioRecord.RECORDSTATE_RECORDING) {
                Logger.e(TAG, "recording state = " + state);
                if (mCallback != null) {
                    mCallback.onAudioRecorderError(2);
                }
            }

            ByteBuffer dataBuffer = ByteBuffer.allocate(bufSize);

            File file = new File(mPcmPath);
            if (file.isFile()) {
                file.delete();
            }
            bRecording = true;
            while (bRecording) {
                dataBuffer.clear();
                dataBuffer.order(ByteOrder.LITTLE_ENDIAN);
                if (readAudioData(dataBuffer.array(), 0, bufSize)) {
                    if (mIsSaveRecord) {
                        save(dataBuffer.array(), bufSize, mPcmPath);
                    }
                    if (mCallback != null) {
                        mCallback.onAudioDataChanged(dataBuffer.array(), bufSize);
                    }
                }
            }
        }

        private boolean readAudioData(byte[] data, int offset, int size) {
            int readSize = 0;
            while (bRecording) {
                int state = mAudioRecord.getState();
                if (state != AudioRecord.STATE_INITIALIZED) {
                    Logger.e(TAG, "A state = " + state);
                }
                state = mAudioRecord.getRecordingState();
                if (state != AudioRecord.RECORDSTATE_RECORDING) {
                    Logger.e(TAG, "B state = " + state);
                }
                int thisReadSize = mAudioRecord.read(data, offset + readSize, size - readSize);
                if (false) {
                    data[0] = '\0'; //如果不是正真的录音开始，则直接加结束符
                    continue;
                }
                if (thisReadSize > 0) {
                    readSize += thisReadSize;
                } else {
                    break;
                }
                if (readSize == size) {
                    return true;
                } else if (readSize > size) {
                    break;
                }
            }
            return false;
        }
    }
}
